<?php
class jobs {
  function __construct() {
  }

  public function select() {
    $data = new jqGridData();
    try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

		$rwd = $_SESSION['priv']->jobs->rwd;
		if( !$rwd  ) throw new Exception(ERR_M::DATA_ACC_DISABLED, 99);

// Проверяем данные
        $page    = ( isset($_REQUEST["page"]) && $_REQUEST["page"] > 0 ) ? $_REQUEST["page"] : 1;
        $limit    = ( isset($_REQUEST["rows"]) && $_REQUEST["rows"] > 0 ) ? $_REQUEST["rows"] : 1;
        $sidx    = ( isset($_REQUEST["sidx"]) && $_REQUEST["sidx"] != '' ) ? $_REQUEST["sidx"] : 'job_start';
        $sord    = ( isset($_REQUEST["sord"]) && strtolower($_REQUEST["sord"]) == 'desc' ) ? 'DESC' : 'ASC';
        $search    = ( isset($_REQUEST["_search"]) && strtolower($_REQUEST["_search"]) == 'true') ? true : false;
 
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Сортировка
		switch( $sidx ) {
			case 'job_start': $sidx = 'job.job_start'; break;
			case 'build_name': $sidx = 'build.build_name'; break;
			case 'cli_name': $sidx = 'cli.cli_name'; break;
			case 'jtp_descr': $sidx = 'jtp.jtp_descr'; break;
			case 'job_finish': $sidx = 'job.job_finish'; break;
		}
		
// Фильтр
        $whc = array();
		$jn1 = $jn2 = $jn3 = false;
        if( $search )
        {
            if(isset($_REQUEST["id"]) && is_numeric($_REQUEST["id"]) ) {
				$whc[] = "job.job_id=" . $db->quote($_REQUEST["job_id"]);
		    }
            
            if(isset($_REQUEST["job_start"]) && ( $dt = Tools::date_to_search_str( $_REQUEST["job_start"] )) != "") {
				$whc[] = "job.job_start LIKE " . $db->quote("%".$dt."%");
            }
			
            if(isset($_REQUEST["build_name"]) && $_REQUEST["build_name"] != "") {
				$whc[] = "build.build_name LIKE " . $db->quote("%".$_REQUEST["build_name"]."%");
				$jn1 = true;
            }
			
            if(isset($_REQUEST["cli_name"]) && $_REQUEST["cli_name"] != "") {
		    	$whc[] = "cli.cli_name LIKE " . $db->quote("%".$_REQUEST["cli_name"]."%");
				$jn2 = true;
            }
			
            if(isset($_REQUEST["jtp_descr"]) && $_REQUEST["jtp_descr"] != "") {
		    	$whc[] = "jtp.jtp_descr LIKE " . $db->quote("%".$_REQUEST["jtp_descr"]."%");
				$jn3 = true;
            }
			
            if(isset($_REQUEST["job_finish"]) && ( $dt = Tools::date_to_search_str( $_REQUEST["job_finish"] )) != "") {
				$whc[] = "job.job_finish LIKE " . $db->quote("%".$dt."%");
            }
			
		}

// Фильтр привилегий
		if( $rwd & PERM_BITS::READ_OTH ) $whc[] = '1 = 1';
		elseif( $rwd & PERM_BITS::READ_GRP ) 
			$whc[] = 'job_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::READ_OWN ) $whc[] = 'job_user_id = '.$_SESSION['user_id'];
		else $whc[] = '1 = 0';

// Окончательно формируем условие
        $whereclaus = (count($whc) != 0 ) ? " WHERE " . implode(" AND ", $whc) : "";
		unset($whc);
            
// Получаем количество записей с учетом фильтра
        $join1 = " INNER JOIN builds AS build ON build.build_id = job.job_build_id ";
        $join2 = " INNER JOIN clients AS cli ON cli.cli_id = job.job_cli_id ";
        $join3 = " INNER JOIN jobtypes AS jtp ON jtp.jtp_id = job.job_jtp_id ";
		$join4 = " LEFT JOIN totpays ON totp_job_id = job_id ";
            
        $sql = "SELECT COUNT(*) AS count FROM jobs AS job ".
			($jn1?$join1:'').($jn2?$join2:'').($jn3?$join3:'').$whereclaus;
			
        $rs = $db->query( $sql );
		$count = $rs->fetchObject()->count;
		$rs->closeCursor();
            
// Пустая таблица
        if ($count == 0)
			throw new Exception(ERR_M::SUCCESS, 0);
            
// Рассчитаем сколько всего страниц займут данные в БД
    	if( $count > 0 && $limit > 0) {
    		$total_pages = ceil($count/$limit);
    	} else {
    		$total_pages = 0;
    	}                  
 
		if ($page > $total_pages) $page=$total_pages;                  
 
		$start = $limit*$page - $limit;                   
		if($start < 0) $start = 0;                  
 
// Формируем запрос
		$sql = 	'SELECT  job_id, job_start, job_cost, build_id, build_name, cli_id, cli_name, '.
						'jtp_id, jtp_descr, job_duration, job_finish, job_comment, '.
				'IF( job_finish IS NULL, ( SELECT SUM(incp_amount) FROM incpays WHERE incp_job_id = job_id ), totp_incoming ) as totp_inc, '.
				'IF( job_finish IS NULL, ( SELECT SUM(outp_amount) FROM outpays WHERE outp_job_id = job_id ), totp_outlay ) as totp_out '.
				'FROM jobs AS job '.$join1.$join2.$join3.$join4.$whereclaus." ORDER BY ".$sidx." ".$sord.
		        ' LIMIT '.$start.", ".$limit;

		$rs = $db->query($sql);                  
 
// Формирование объекта для возврата
        
    	$data->page      = $page;
    	$data->total     = $total_pages;
    	$data->records   = $count;
    		
    // Строки данных для таблицы
		$i = 0;
		while($row = $rs->fetchObject() ) {
			$status = ( Tools::how_many('sec', $row->job_start) > $row->job_duration * 86400 ) ? 0x1 : 0;
			
			$totp_inc = is_null( $row->totp_inc ) ? 0 : $row->totp_inc;
			$totp_out = is_null( $row->totp_out ) ? 0 : $row->totp_out;

			$data->rows[$i]['id'] = $row->job_id;
			$data->rows[$i]['cell'] = array(
					$status,
					$row->job_id,
					Tools::str_iso2local_date( $row->job_start ),
					$row->job_cost,
					$row->build_id,
					$row->build_name,
					$row->cli_id,
					$row->cli_name,
					$row->jtp_id,
					$row->jtp_descr,
					$row->job_duration,
					Tools::str_iso2local_date( $row->job_finish ),
					$totp_inc,
					$totp_out,
					$totp_inc - $totp_out,
					$row->job_comment
		    );
		    $i++;
		}
    }
	catch (PDOException $e) {
		$data->userdata->errcode = 1;
		$data->userdata->errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$data->userdata->errcode = $e->getCode();
		$data->userdata->errmess = $e->getMessage();
	}
 
	return $data;
  }
  

// 
// Поиск в !!! НЕ ЗАКРЫТЫХ работах
//  
  public function find() {
	$errcode = 0;
	$errmess = '';
    $data = array();
	
    try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

// Find - услуга общего пользования. Поэтому, маска должна быть READ_ANY
		$rwd = $_SESSION['priv']->jobs->rwd;
		if( ( $rwd & PERM_BITS::READ_ANY ) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Проверяем данные
        $term   = isset($_REQUEST["term"]) ? trim(strip_tags($_REQUEST["term"])) : '';
        if ($term == '')
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
   
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Формируем запрос
		$trm = $db->quote("%".$term."%");
		$sql =	"SELECT job_id, jtp_id, jtp_descr, build_id, build_name FROM builds ".
				"INNER JOIN jobs ON jobs.job_build_id = builds.build_id AND jobs.job_finish IS NULL ".
				"INNER JOIN jobtypes ON jobtypes.jtp_id = jobs.job_jtp_id ".
				"WHERE build_name LIKE ${trm}";
		$rs = $db->query($sql);                  
        while( $row = $rs->fetchObject() ) {
            $unit = new stdClass;			
			$num = mb_strlen($term,'UTF-8');
			
			$pos = mb_stripos( $row->build_name, $term, 0, 'UTF-8');
			if( !is_bool($pos) ) {
				$bterm = mb_substr($row->build_name, $pos, $num, 'UTF-8');
				$build_name = preg_replace( '/'.$bterm.'/u', '<b>'.$bterm.'</b>', $row->build_name );				
			} else $build_name = $row->build_name;
			
			$pos = mb_stripos( $row->jtp_descr, $term, 0, 'UTF-8');
			if( !is_bool($pos) ) {
				$bterm = mb_substr($row->jtp_descr, $pos, $num, 'UTF-8');
				$jtp_descr = preg_replace( '/'.$bterm.'/u', '<b>'.$bterm.'</b>', $row->jtp_descr );				
			} else $jtp_descr = $row->jtp_descr;
			
			$unit->build_name = $build_name;
			$unit->jtp_descr = $jtp_descr;
            $unit->build_id = $row->build_id;
            $unit->job_id = $row->job_id;
            $unit->jtp_id = $row->jtp_id;
            $data[] = $unit;
        }
        $rs->closeCursor();
		
		$sel_itp = $db->prepare("SELECT itp_id, itp_code, itp_descr FROM inctypes WHERE itp_jtp_id = :jtp_id ORDER BY itp_ord");
		$sel_otp = $db->prepare("SELECT otp_id, otp_code, otp_descr FROM outtypes WHERE otp_jtp_id = :jtp_id ORDER BY otp_ord");
		for( $i=0; $i<count($data); $i++ ) {
			$data[$i]->itp = array();
			$sel_itp->execute( array( ':jtp_id'=>$data[$i]->jtp_id ) );
			while( $row = $sel_itp->fetchObject() ) {
				$unit = new stdClass;
				$unit->itp_id = $row->itp_id;
				$unit->itp_code = $row->itp_code;
				$unit->itp_descr = $row->itp_descr;
				$data[$i]->itp[] = $unit;
			}
			$sel_itp->closeCursor();

			$data[$i]->otp = array();
			$sel_otp->execute( array( ':jtp_id'=>$data[$i]->jtp_id ) );
			while( $row = $sel_otp->fetchObject() ) {
				$unit = new stdClass;
				$unit->otp_id = $row->otp_id;
				$unit->otp_code = $row->otp_code;
				$unit->otp_descr = $row->otp_descr;
				$data[$i]->otp[] = $unit;
			}
			$sel_otp->closeCursor();
		}
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}
 
	return array('errcode'=>$errcode,'errmess'=>$errmess, 'data'=>$data);
  }
  
  
// 
// Получить список видов платежей по заданному идентификатору работы
//  
  public function getpmcodes() {
	$errcode = 0;
	$errmess = '';
    $data = new stdClass;
	
    try
    {
// Только зарегистрированные пользователи могут видеть данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

// Find - услуга общего пользования. Поэтому, маска должна быть READ_ANY
		$rwd = $_SESSION['priv']->jobs->rwd;
		if( ( $rwd & PERM_BITS::READ_ANY ) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Проверяем данные
        $job_id   = isset($_REQUEST["job_id"]) ? trim(strip_tags($_REQUEST["job_id"])) : '';
        if ( !$job_id || !ctype_digit($job_id) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
   
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Определяем тип работы
		$rs = $db->query( "SELECT job_jtp_id FROM jobs WHERE job_id = ${job_id}" );                  
        $row = $rs->fetchObject();
		if( !$row ) throw new Exception("Тип работы не найден. Обратитесь к администратору системы.", 1);
		$jtp_id = $row->job_jtp_id;
		$rs->closeCursor();
		
		$sel_btp = $db->prepare("SELECT btp_id, btp_code, btp_descr FROM baltypes WHERE btp_jtp_id = :jtp_id ORDER BY btp_ord");
		$sel_itp = $db->prepare("SELECT itp_id, itp_code, itp_descr, itp_btp_id FROM inctypes WHERE itp_jtp_id = :jtp_id ORDER BY itp_ord");
		$sel_otp = $db->prepare("SELECT otp_id, otp_code, otp_descr, otp_btp_id FROM outtypes WHERE otp_jtp_id = :jtp_id ORDER BY otp_ord");

// Баланс
		$data->btp = array();
		$sel_btp->execute( array( ':jtp_id'=>$jtp_id ) );
		while( $row = $sel_btp->fetchObject() ) {
			$unit = new stdClass;
			$unit->btp_id = $row->btp_id;
			$unit->btp_code = $row->btp_code;
			$unit->btp_descr = $row->btp_descr;
			$data->btp[] = $unit;
		}
		$sel_itp->closeCursor();

// Исходящие
		$data->otp = array();
		$sel_otp->execute( array( ':jtp_id'=>$jtp_id ) );
		while( $row = $sel_otp->fetchObject() ) {
			$unit = new stdClass;
			$unit->otp_id = $row->otp_id;
			$unit->otp_code = $row->otp_code;
			$unit->otp_btp_id = $row->otp_btp_id;
			$unit->otp_descr = $row->otp_descr;
			$data->otp[] = $unit;
		}
		$sel_otp->closeCursor();
		
// Входящие		
		$data->itp = array();
		$sel_itp->execute( array( ':jtp_id'=>$jtp_id ) );
		while( $row = $sel_itp->fetchObject() ) {
			$unit = new stdClass;
			$unit->itp_id = $row->itp_id;
			$unit->itp_code = $row->itp_code;
			$unit->itp_btp_id = $row->itp_btp_id;
			$unit->itp_descr = $row->itp_descr;
			$data->itp[] = $unit;
		}
		$sel_itp->closeCursor();
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}
 
	return array('errcode'=>$errcode,'errmess'=>$errmess, 'data'=>$data);
  }
  
  
  public function insert() {

    $job_start		= isset($_POST['job_start']) ? trim(strip_tags($_POST['job_start'])) : '';
    $job_cost		= isset($_POST['job_cost']) ? trim(strip_tags($_POST['job_cost'])) : '';
    $build_id		= isset($_POST['build_id']) ? trim(strip_tags($_POST['build_id'])) : '';
    $build_name		= isset($_POST['build_name']) ? trim(strip_tags($_POST['build_name'])) : '';
    $cli_id			= isset($_POST['cli_id']) ? trim(strip_tags($_POST['cli_id'])) : '';
    $jtp_id			= isset($_POST['jtp_id']) ? trim(strip_tags($_POST['jtp_id'])) : '';
    $job_duration	= isset($_POST['job_duration']) ? trim(strip_tags($_POST['job_duration'])) : '';
    $job_comment	= isset($_POST['prod_comment']) ? trim(strip_tags($_POST['job_comment'])) : '';
	
	$errcode = 0;
	$errmess = '';
	$data = new stdClass;
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$job_user_id = $spr_user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на вставку записей
		$rwd = $_SESSION['priv']->jobs->rwd;
		if( !$rwd & PERM_BITS::WRITE_OWN ) 
			throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// Открываем базу данных
		$db = App::me()->dbOpen();
		
// job_start
		if( !$job_start ) $errdata .= ($errdata?'<br>':'')."- Не указана дата начала работы.";
		elseif( ( $job_start = Tools::check_date($job_start) ) === false )
			$errdata .= ($errdata?'<br>':'')."- Дата начала работы указана неправильно.";
			
// job_cost
		if( !$job_cost ) 
			$errdata .= ($errdata?'<br>':'')."- Не указана стоимость работы.";
		else {
			$job_cost = str_replace(',', '.', $job_cost);
			if( !is_numeric($job_cost) ) 
				$errdata .= ($errdata?'<br>':'')."- Неправильно указана стоимость работы.";
		}

// build_id & build_name
		if( !$build_id || !ctype_digit( $build_id ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор объекта.';
		else {
			$rs = $db->query('SELECT build_name FROM builds WHERE build_id = '.$db->quote($build_id));                  
			$row = $rs->fetchObject();
			if( !$row || $row->build_name != $build_name)
				$errdata .= ($errdata?'<br>':'').'- Неправильно указан идентификатор объекта.';
			$rs->closeCursor();
		}

// cli_id
		if( !$cli_id || !ctype_digit( $cli_id ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор заказчика.';

// jtp_id
		if( !$jtp_id || !ctype_digit( $jtp_id ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор вида работ.';

// job_duration
		if( !$job_duration || !ctype_digit( $job_duration ) ) 
			$errdata .= ($errdata?'<br>':'').'- Не указана планируемая продолжительность работы.';

//				
// Если есть ошибки - можно уже закончить
//
		if( $errdata ) throw new Exception($errdata, 1);

// Проверяем, может быть существует уже открытая работа того же вида
// либо с аналогичной датой начала.
		$sql =	"SELECT 1 FROM jobs WHERE job_build_id = ${build_id} AND job_jtp_id = ${jtp_id} ".
					"AND ( job_finish IS NULL OR job_start = ${job_start} ) LIMIT 1";
		$rs = $db->query( $sql );
		if( $rs->fetchObject() )
			throw new Exception("Для объекта '${build_name}' либо существует открытая работа того же вида, ".
						"либо аналогичная работа с той же датой начала.", 1);
		$rs->closeCursor();
		
// Пишем данные
		$sql =	"INSERT jobs SET ".
					"job_start = ".$db->quote($job_start).", ".
					"job_cost = " .$db->quote($job_cost).", ".
					"job_build_id = ".$db->quote($build_id).", ".
					"job_cli_id = ".$db->quote($cli_id).", ".
					"job_jtp_id = ".$db->quote($jtp_id).", ".
					"job_duration = ".$db->quote($job_duration).", ".
					"job_comment = ".$db->quote($job_comment);
		$db->exec($sql);
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess, 'data'=>$data);
  }

/*
	Правила редактирования работ.
	1.	Все параметры работы (кроме даты закрытия) могут быть изменены до появления
		каких либо платежей.
	2.	После появления каких либо платежей может быть проставлена только дата закрытия 
		работы и коментарий.
*/
  
  public function update() {

    $job_id			= isset($_POST['id']) ? trim(strip_tags($_POST['id'])) : '';
    $job_start		= isset($_POST['job_start']) ? trim(strip_tags($_POST['job_start'])) : '';
    $job_finish		= isset($_POST['job_finish']) ? trim(strip_tags($_POST['job_finish'])) : '';
    $job_cost		= isset($_POST['job_cost']) ? trim(strip_tags($_POST['job_cost'])) : '';
    $build_id		= isset($_POST['build_id']) ? trim(strip_tags($_POST['build_id'])) : '';
    $build_name		= isset($_POST['build_name']) ? trim(strip_tags($_POST['build_name'])) : '';
    $cli_id			= isset($_POST['cli_id']) ? trim(strip_tags($_POST['cli_id'])) : '';
    $jtp_id			= isset($_POST['jtp_id']) ? trim(strip_tags($_POST['jtp_id'])) : '';
    $job_duration	= isset($_POST['job_duration']) ? trim(strip_tags($_POST['job_duration'])) : '';
    $job_comment	= isset($_POST['job_comment']) ? trim(strip_tags($_POST['job_comment'])) : '';
		
	$errcode = 0;
	$errmess = '';
	
    try
    {
// Только зарегистрированные пользователи могут вносить данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);
		$job_user_id = $_SESSION['user_id'];
		
//Есть ли разрешение на изменение записей
		$rwd = $_SESSION['priv']->jobs->rwd;
		if( ($rwd & PERM_BITS::WRITE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);

// Все ли данные переданы
		$errdata = "";
		
// job_id
        if (!$job_id || !ctype_digit( $job_id ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
		
// Открываем базу данных
		$db = App::me()->dbOpen();

		$q_job_id = $db->quote($job_id);
		
// Есть ли разрешение на апдейт этой записи
		if( $rwd & PERM_BITS::WRITE_OTH ) $where = '1 = 1';
		elseif( $rwd & PERM_BITS::WRITE_GRP ) 
			$where = 'job_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::WRITE_OWN ) $where = 'job_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= " AND job_id = ${q_job_id}";
		
		$rs = $db->query( 'SELECT job_start, job_finish FROM jobs WHERE '.$where.' LIMIT 1' );		
		$row = $rs->fetchObject();
		if( !$row ) throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
		$ostart = $row->job_start;
		$ofinish = $row->job_finish;
		$rs->closeCursor();
		
// Формируем статус и состояние спецификации
		$status = 0;
		$state = $this->getjobstate( $db, $job_id );
		
// Разные этапы
		$sql = '';
		switch( $state[0]->state & 0x6 ) {
		case 0:		// Еще не было платежей и работа не закрыта		
			// job_start
			if( !$job_start ) $errdata .= ($errdata?'<br>':'')."- Не указана дата начала работы.";
			elseif( ( $job_start = Tools::check_date($job_start) ) === false )
					$errdata .= ($errdata?'<br>':'')."- Дата начала работы указана неправильно.";
			
			// job_cost
			if( !$job_cost )
				$errdata .= ($errdata?'<br>':'')."- Не указана стоимость работы.";
			else {
				$job_cost = str_replace(',', '.', $job_cost);
				if( !is_numeric($job_cost) ) 
					$errdata .= ($errdata?'<br>':'')."- Неправильно указана стоимость работы.";
			}
		
			// build_id & build_name
			if( !$build_id || !ctype_digit( $build_id ) ) 
				$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор объекта.';
			else {
				$rs = $db->query('SELECT build_name FROM builds WHERE build_id = '.$db->quote($build_id));                  
				$row = $rs->fetchObject();
				if( !$row || $row->build_name != $build_name)
					$errdata .= ($errdata?'<br>':'').'- Неправильно указан идентификатор объекта.';
				$rs->closeCursor();
			}

			// cli_id
			if( !$cli_id || !ctype_digit( $cli_id ) ) 
				$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор заказчика.';

			// jtp_id
			if( !$jtp_id || !ctype_digit( $jtp_id ) ) 
				$errdata .= ($errdata?'<br>':'').'- Не указан идентификатор вида работ.';

			// job_duration
			if( !$job_duration || !ctype_digit( $job_duration ) ) 
				$errdata .= ($errdata?'<br>':'').'- Не указана планируемая продолжительность работы.';

			//				
			// Если есть ошибки - можно уже закончить
			//
			if( $errdata ) throw new Exception($errdata, 1);

			// Проверяем, может быть существует уже открытая работа того же вида
			// либо с аналогичной датой начала.
			$sql =	"SELECT 1 FROM jobs WHERE job_id != ${q_job_id} ".
						"AND job_build_id = ${build_id} AND job_jtp_id = ${jtp_id} ".
						"AND ( job_finish IS NULL OR job_start = ${job_start} ) LIMIT 1";
			$rs = $db->query( $sql );
			if( $rs->fetchObject() )
				throw new Exception("Для объекта '${build_name}' либо существует открытая работа того же вида, ".
									"либо аналогичная работа с той же датой начала.", 1);
			$rs->closeCursor();

			// Пишем данные
			$sql =	"UPDATE jobs SET ".
						"job_start = ".$db->quote($job_start).", ".
						"job_cost = " .$db->quote($job_cost).", ".
						"job_build_id = ".$db->quote($build_id).", ".
						"job_cli_id = ".$db->quote($cli_id).", ".
						"job_jtp_id = ".$db->quote($jtp_id).", ".
						"job_duration = ".$db->quote($job_duration).", ".
						"job_comment = ".$db->quote($job_comment)." ".
					"WHERE job_id = ${q_job_id}";
					
			$db->exec($sql);
			break;
			
		case 2:		// Платежи начали поступать. Может быть закрітие работы.

			// job_cost
			if( !$job_cost )
				$errdata .= ($errdata?'<br>':'')."- Не указана стоимость работы.";
			else {
				$job_cost = str_replace(',', '.', $job_cost);
				if( !is_numeric($job_cost) ) 
					$errdata .= ($errdata?'<br>':'')."- Неправильно указана стоимость работы.";
			}
		
			// job_duration
			if( !$job_duration || !ctype_digit( $job_duration ) ) 
				$errdata .= ($errdata?'<br>':'').'- Не указана планируемая продолжительность работы.';

			if( $job_finish ) {
				if( ( $job_finish = Tools::check_date($job_finish) ) === false )
					$errdata .= ($errdata?'<br>':'')."- Дата закрытия работы указана неправильно.";
				elseif( $job_finish < $ostart )
					$errdata .= ($errdata?'<br>':'')."- Дата закрытия работы не может быть меньше даты начала.";
			}
			
			if( $errdata ) throw new Exception( $errdata, 1);
				
			if( !$job_finish ) {
				$sql =	"UPDATE jobs SET ".
							"job_cost = ".$db->quote($job_cost).", ".
							"job_duration = ".$db->quote($job_duration).", ".
							"job_comment = ".$db->quote($job_comment)." ".
						"WHERE job_id = ${q_job_id}";
				$db->exec($sql);
				break;
			}
			
			$sql1 =	"UPDATE jobs SET ".
						"job_cost = ".$db->quote($job_cost).", ".
						"job_duration = ".$db->quote($job_duration).", ".
						"job_finish = ".$db->quote($job_finish).", ".
						"job_comment = ".$db->quote($job_comment)." ".
					"WHERE job_id = ${q_job_id}";
			$sql2 = "REPLACE totpays SET totp_job_id = ${q_job_id}, ".
					"totp_incoming = IFNULL(( SELECT SUM( incp_amount ) FROM incpays WHERE incp_job_id = ${q_job_id} ),0), ".
					"totp_outlay = IFNULL(( SELECT SUM( outp_amount ) FROM outpays WHERE outp_job_id = ${q_job_id} ),0)";
					
			try {
				$db->beginTransaction();
				$db->exec( $sql1.'; '.$sql2);
				$db->commit();
			}
			catch (PDOException $e) {
				$db->rollback();
				$errcode = 1;
				$errmess = ERR_M::DATABASE_ERROR;
			}
			break;
		
		case 4:		// Запрещенная комбинация
				throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
				break;
				
		case 6:		// Платежи начали поступать и дата зактытия уже присутствует		
			if( !$job_finish ) $errdata .= ($errdata?'<br>':'')."- Не указана дата закрытия работы.";
			elseif( ( $job_finish = Tools::check_date($job_finish) ) === false )
				$errdata .= ($errdata?'<br>':'')."- Дата закрытия работы указана неправильно.";
			elseif( $job_finish < $ostart )
				$errdata .= ($errdata?'<br>':'')."- Дата закрытия работы не может быть меньше даты начала.";
				
			if( $errdata )
				throw new Exception( $errdata, 1);
				
			$sql =	"UPDATE jobs SET ".
						"job_finish  = ".$db->quote($job_finish).", ".
						"job_comment = ".$db->quote($job_comment)." ".
					"WHERE job_id = ".$db->quote($job_id);
			$db->exec($sql);
			break;
		}
		
    }
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

  
  public function delete() {
    $errmess = "";
	$errcode = 0;
		
    $job_ids = (isset($_REQUEST['id']) && $_REQUEST['id'] != '') ?
        explode(",", $_REQUEST["id"], 100) : array();


    try
    {
// Только зарегистрированные пользователи могут удалять данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

        if (!Tools::is_numeric_array( $job_ids ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
		$job_id_list = implode(',', $job_ids);

// Есть ли разрешение на удаление хоть каких либо записей jobs
		$rwd = $_SESSION['priv']->jobs->rwd;
		if( ($rwd & PERM_BITS::DELETE_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);
		
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Есть ли разрешение на удаление записей jobs
		if( $rwd & PERM_BITS::DELETE_OTH ) $where = '1 = 1';
		elseif( $rwd & PERM_BITS::DELETE_GRP ) 
			$where = 'job_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::DELETE_OWN ) $where = 'job_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND job_id IN ('.$job_id_list.')';
		
		$rs = $db->query( "SELECT COUNT(*) as count FROM jobs WHERE ${where}" );		
		$row = $rs->fetchObject();
		if( $row->count != count($job_ids) )
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
		$rs->closeCursor();

// Нет ли платежей
		$state = $this->getjobstate( $db, $job_ids );
		$errdata = '';
		for( $i=0; $i<count($job_ids); $i++ ) {
			if( $state[$i]->state & 0x6 )
				$errdata .= ($errdata?', ':'')."'".$state[$i]->id."'"; 
		}
		if( $errdata != '' )
			throw new Exception( "По удаляемым работам ${errdata} уже имеются платежи либо они уже закрыты.", 1);
		
// Удаляем записи
		$db->exec( "DELETE FROM jobs WHERE job_id IN ( ${job_id_list} )" );
	}
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess);
  }

  
// Получить состояние платежей по работам
  public function jobstate() {
    $errmess = "";
	$errcode = 0;
	$jobstate = 0x4;	// Запрещенное состояние
    try
    {
// Только зарегистрированные пользователи
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

// Идентификатор изделия
		$job_ids = isset($_REQUEST['job_ids']) ? $_REQUEST['job_ids'] : '';
		if( !Tools::is_numeric_array($job_ids ) )
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);

// Открываем базу данных
		$db = App::me()->dbOpen();	
		$jobstate = $this->getjobstate( $db, $job_ids );
	}
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	return array('errcode'=>$errcode,'errmess'=>$errmess,'data'=>$jobstate);
  }
  
  private function getjobstate( $db, $job_ids ) {
	if( is_array($job_ids) )
		$job_id_list = implode( ', ', $job_ids );
	elseif( is_string($job_ids) ) {
		$job_id_list = $job_ids;
		$job_ids = explode(',', $job_id_list);
	}
	elseif( is_scalar( $job_ids ) ) {
		$job_id_list = strval($job_ids);
		$job_ids = array( $job_ids );
	}
	$state = array();
	
/* Пока заглушка
	for( $i=0; $i<count($job_ids); $i++ ) {
		$unit = new stdClass;
		$unit->id = $job_ids[$i];
		$unit->state = 0;
		$state[] = $unit;
	}
*/
	$sql =	"SELECT job_id, ".
			"IF(( SELECT 1 FROM incpays WHERE incp_job_id = job_id limit 1 ),2,0) | ".
			"IF(( SELECT 1 FROM outpays WHERE outp_job_id = job_id limit 1 ),2,0) | ".
			"IF( job_finish IS NULL,0,4) AS state FROM jobs WHERE job_id IN ( ${job_id_list} )";
	$rs = $db->query( $sql );
	while( $row = $rs->fetchObject() ) {
		$unit = new stdClass;
		$unit->id = $row->job_id;
		$unit->state = $row->state;
		$state[] = $unit;
	}
	$rs->closeCursor();

	return $state;
  }

  
  public function selbal() {
    $errmess = "";
	$errcode = 0;
	$job = array();
		
    $job_ids = (isset($_REQUEST['job_ids']) && $_REQUEST['job_ids'] != '') ?
        explode(",", $_REQUEST["job_ids"]) : array();


    try
    {
// Только зарегистрированные пользователи могут удалять данные
		if( !isset($_SESSION['user_id']) || $_SESSION['user_id'] == 0 )
			throw new Exception(ERR_M::TIMEOUT_ERROR, 999);

        if (!Tools::is_numeric_array( $job_ids ))
			throw new Exception(ERR_M::PROTOCOL_ERROR, 1);
        
		$job_id_list = implode(',', $job_ids);

// Есть ли разрешение на чтение хоть каких либо записей jobs
		$rwd = $_SESSION['priv']->jobs->rwd;
		if( ($rwd & PERM_BITS::READ_ANY) == 0 ) throw new Exception(ERR_M::DATA_OPER_DISABLED, 99);
		
// Открываем базу данных
		$db = App::me()->dbOpen();	

// Есть ли разрешение на чтение записей jobs
		if( $rwd & PERM_BITS::READ_OTH ) $where = '1 = 1';
		elseif( $rwd & PERM_BITS::READ_GRP ) 
			$where = 'job_user_id IN (SELECT user_id FROM users WHERE cat_id = '.$_SESSION['cat_id'].')';
		elseif( $rwd & PERM_BITS::READ_OWN ) $where = 'job_user_id = '.$_SESSION['user_id'];
		else $where = '1 = 0';
		$where .= ' AND job_id IN ('.$job_id_list.')';
		
		$rs = $db->query( "SELECT COUNT(*) as count FROM jobs WHERE ${where}" );		
		$row = $rs->fetchObject();
		if( $row->count != count($job_ids) )
			throw new Exception(ERR_M::DATA_OWNER_DISABLED, 99);
		$rs->closeCursor();

		$job = $this->BuildBalance( $db, $job_id_list );
	}
	catch (PDOException $e) {
		$errcode = 1;
		$errmess = ERR_M::DATABASE_ERROR;
	}
    catch ( Exception $e ) {
		$errcode = $e->getCode();
		$errmess = $e->getMessage();
	}

	$data = new stdClass;
	$data->errcode = $errcode;
	$data->errmess = $errmess;
	$data->job = $job;
	return $data;
  }

  private function BuildBalance( $db, $job_id_list, $whr = '1 = 1' ) {
  
// Формируем структуру баланса и платежей для каждой из работ
	$job = $this->BuildBalTree( $db, $job_id_list );
	
// Выбираем и раскидываем входящие платежи
	$sql =	"SELECT incp_job_id, incp_itp_id, incp_amount FROM incpays WHERE incp_job_id IN ( ${job_id_list} ) AND ${whr}";
	$rs = $db->query( $sql );
	while( $row = $rs->fetchObject() ) {
		for( $i=0; $i<count($job); $i++ ) {
			if( $job[$i]->id == $row->incp_job_id ) {
				for( $m=0; $m<count($job[$i]->inc); $m++ ) {
					if( $job[$i]->inc[$m]->itp_id == $row->incp_itp_id ) {
						$job[$i]->inc[$m]->sum += $row->incp_amount;
						break;
					}
				}
				break;
			}
		}
	}
	$rs->closeCursor();
	
// Выбираем и раскидываем исходящие платежи
	$sql =	"SELECT outp_job_id, outp_otp_id, outp_amount FROM outpays WHERE outp_job_id IN ( ${job_id_list} ) AND ${whr}";
	$rs = $db->query( $sql );
	while( $row = $rs->fetchObject() ) {
		for( $i=0; $i<count($job); $i++ ) {
			if( $job[$i]->id == $row->outp_job_id ) {
				for( $m=0; $m<count($job[$i]->out); $m++ ) {
					if( $job[$i]->out[$m]->otp_id == $row->outp_otp_id ) {
						$job[$i]->out[$m]->sum += $row->outp_amount;
						break;
					}
				}
				break;
			}
		}
	}
	$rs->closeCursor();
	
// Формируем баланс по группам платежей
	for( $i=0; $i<count($job); $i++ ) {
		for( $m = 0; $m < count($job[$i]->bal); $m++ ) {
			$sum = 0;
			for( $n = 0; $n < count($job[$i]->inc); $n++ ) {
				if( $job[$i]->inc[$n]->btp_id == $job[$i]->bal[$m]->btp_id ) 
					$sum += $job[$i]->inc[$n]->sum;
			}
			$job[$i]->bal[$m]->sum += $sum;
			$job[$i]->itot += $sum;
			
			$sum = 0;
			for( $n = 0; $n < count($job[$i]->out); $n++ ) {
				if( $job[$i]->out[$n]->btp_id == $job[$i]->bal[$m]->btp_id ) 
					$sum += $job[$i]->out[$n]->sum;
			}
			$job[$i]->bal[$m]->sum -= $sum;			
			$job[$i]->otot += $sum;
		}
		$job[$i]->btot = $job[$i]->itot - $job[$i]->otot;
	}
	
	return $job;
  }

  
// 
// Для каждой из работ формирует пустую структуру баланса и платежей,
// соответствующую типу работы.
//
  private function BuildBalTree( $db, $job_id_list ) {
	
// Определяем тип для каждой из работ
	$job = array();
	$sql =	"SELECT job_id, build_name, job_cost, job_start, job_finish, job_jtp_id, jtp_descr FROM jobs ".
			"INNER JOIN jobtypes ON jtp_id = job_jtp_id ".
			"INNER JOIN builds ON build_id = job_build_id ".
			"WHERE job_id IN ( ${job_id_list} ) ORDER BY build_name";
			
	$rs = $db->query( $sql );
    while( $row = $rs->fetchObject() ) {
		$unit = new stdClass;
		$unit->id = $row->job_id;
		$unit->jtp_id = $row->job_jtp_id;
		$unit->build_name = $row->build_name;
		$unit->jtp_descr = $row->jtp_descr;
		$unit->job_cost = $row->job_cost;
		$unit->job_start = Tools::str_iso2local_date( $row->job_start );
		$unit->job_finish = Tools::str_iso2local_date( $row->job_finish );
		$unit->itot = 0;
		$unit->otot = 0;
		$unit->btot = 0;
		$job[] = $unit;
	}
	$rs->closeCursor();
	
// Формируем массив уникальных типов работ.
	$jt = array();
	for( $i=0; $i<count($job); $i++ )
		if( !in_array( $job[$i]->jtp_id, $jt ) ) $jt[] = $job[$i]->jtp_id;

	$jt_list = implode(',', $jt);
	
// Подготавливаем запросы		
	$sel_btp = $db->prepare("SELECT btp_id, btp_code FROM baltypes WHERE btp_jtp_id = :jtp_id ORDER BY btp_ord");
	$sel_itp = $db->prepare("SELECT itp_id, itp_code, itp_btp_id FROM inctypes WHERE itp_jtp_id = :jtp_id ORDER BY itp_ord");
	$sel_otp = $db->prepare("SELECT otp_id, otp_code, otp_btp_id FROM outtypes WHERE otp_jtp_id = :jtp_id ORDER BY otp_ord");

// Для каждого типа работы считываем список балансов
	for( $i=0; $i<count($jt); $i++ ) {
		$bal = array();
		$sel_btp->execute( array( ':jtp_id'=>$jt[$i] ) );
		while( $row = $sel_btp->fetchObject() ) {
			$unit = new stdClass;
			$unit->btp_id = $row->btp_id;
			$unit->btp_code = $row->btp_code;
			$unit->sum = 0;
			$bal[] = $unit;
		}
		$sel_btp->closeCursor();
	
// Исходящие
		$out = array();
		$sel_otp->execute( array( ':jtp_id'=>$jt[$i] ) );
		while( $row = $sel_otp->fetchObject() ) {
			$unit = new stdClass;
			$unit->otp_id = $row->otp_id;
			$unit->otp_code = $row->otp_code;
			$unit->btp_id = $row->otp_btp_id;
			$unit->sum = 0;
			$out[] = $unit;
		}
		$sel_otp->closeCursor();
		
// Входящие		
		$inc = array();
		$sel_itp->execute( array( ':jtp_id'=>$jt[$i] ) );
		while( $row = $sel_itp->fetchObject() ) {
			$unit = new stdClass;
			$unit->itp_id = $row->itp_id;
			$unit->itp_code = $row->itp_code;
			$unit->btp_id = $row->itp_btp_id;
			$unit->sum = 0;
			$inc[] = $unit;
		}
		$sel_itp->closeCursor();
		
// Клонируем шаблоны конкретным работам
		for( $k = 0; $k < count($job); $k++ ) {
			if( $job[$k]->jtp_id == $jt[$i] ) {
				$job[$k]->bal = array();
				for( $n=0; $n<count($bal); $n++ ) $job[$k]->bal[] = clone $bal[$n];
				
				$job[$k]->inc = array();
				for( $n=0; $n<count($inc); $n++ ) $job[$k]->inc[] = clone $inc[$n];
				
				$job[$k]->out = array();
				for( $n=0; $n<count($out); $n++ ) $job[$k]->out[] = clone $out[$n];
			}
		}

	}
	
	return $job;
  }
} // End od Products class
?>